package com.livestocksolutions;

import java.util.Calendar;
import java.util.HashMap;

import com.livestocksolutions.util.UpdateHistory;

/* -- Version History --
 *  1.0 : 15 July 2012 - Initial version.
 *
 *  1.1 : 16 July 2012 - Class now implements Serializable
 *  
 *  1.2 : 05 September 2012 - Added private final long id and public long getID()
 *  
 *  1.3 : 24 September 2012 - Added Update methods that will allow the comparing and updation of a similar Object.
 *                       Serialisation method no longer requires CattleBreed implementing Serializable.
 *                       
 *  1.4 : 13 October 2012 - Split averageFullWeight, averageDailyGain and averagePubityAgeInDays to differentiate
 *                       differences between Male and Female cattle.
 */

/**
 * Provides data for a particular Breed of Cattle
 * @author Adam Parr
 * 
 * @version 1.4
 * 
 * @.modified 13 October 2012
 */
public class CattleBreed{
	/**
	 * Unique ID that identifies this CattleBreed
	 */
	protected final long id;
	/**
	 * Keep track of any changes, until this CattleBreed is assigned to an owner.
	 */
	protected HashMap<String,UpdateHistory> update = new HashMap<String,UpdateHistory>();
	/**
	 * Name of this Breed
	 */
	private String name;
	/**
	 * Approximate birth weight of this breed
	 */
	private double averageCalfBirthWeight;
	/**
	 * Approximate full grown weight for this breed
	 */
	private double averageFullWeight, averageMaleFullWeight;
	/**
	 * Average weight gain between birth and pubity
	 */
	private double averageDailyGain, averageMaleDailyGain;
	/**
	 * Average time for gestation length in days
	 */
	private int gestationPeriodInDays;
	/**
	 * Average number of days until pubity begins
	 */
	private int averagePubityAgeInDays, averageMalePubityAgeInDays;
	
	/**
	 * Constructor to create a breed
	 * @param name The name of this breed
	 * @param averageCalfBirthWeight Approximate birth weight of this breed
	 * @param averageFullWeight Approximate full grown weight for this breed
	 * @param averageDailyGain Average weight gain between birth and pubity
	 * @param gestationPeriodInDays Average time for gestation length in days
	 * @param averagePubityAgeInDays Average number of days until pubity begins
	 */
	CattleBreed(String name, double averageCalfBirthWeight, double averageFullWeight, double averageMaleFullWeight, double averageDailyGain, double averageMaleDailyGain, int gestationPeriodInDays, int averagePubityAgeInDays, int averageMalePubityAgeInDays){
		
		this.name=name!=null?name.substring(0,1).toUpperCase()+name.substring(1,name.length()):"";
		this.averageCalfBirthWeight=averageCalfBirthWeight;
		this.averageFullWeight=averageFullWeight;
		this.averageMaleFullWeight=averageMaleFullWeight;
		this.averageDailyGain=averageDailyGain;
		this.averageMaleDailyGain=averageMaleDailyGain;
		this.gestationPeriodInDays=gestationPeriodInDays;
		this.averagePubityAgeInDays=averagePubityAgeInDays;
		this.averageMalePubityAgeInDays=averageMalePubityAgeInDays;
		
		update.put("NAME", new UpdateHistory(this.name));
		update.put("BIRTHWEIGHT", new UpdateHistory(String.valueOf(this.averageCalfBirthWeight)));
		update.put("ADULTWEIGHT", new UpdateHistory(String.valueOf(this.averageFullWeight)));
		update.put("DAILYGAIN", new UpdateHistory(String.valueOf(this.averageDailyGain)));
		update.put("GESTATIONLENGTH", new UpdateHistory(String.valueOf(this.gestationPeriodInDays)));
		update.put("PUBITY", new UpdateHistory(String.valueOf(this.averagePubityAgeInDays)));
		
		update.put("DAILYMALEGAIN", new UpdateHistory(String.valueOf(this.averageMaleDailyGain)));
		update.put("MALEFULLWEIGHT", new UpdateHistory(String.valueOf(this.averageMaleFullWeight)));
		update.put("MALEPUBITY", new UpdateHistory(String.valueOf(this.averageMalePubityAgeInDays)));
		
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getOwnerByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
	}
	
	/**
	 * Get the name of this breed
	 * @return name of this Breed
	 */
	public String getBreedName(){
		return name;
	}
	
	/**
	 * Get the approximate birth weight of this breed
	 * @return Approximate birth weight of this breed
	 */
	public double getAverageCalfBirthWeight(){
		return averageCalfBirthWeight;
	}

	/**
	 * Get the approximate full grown weight for this breed
	 * @return Approximate full grown weight for this breed
	 */
	public double getAverageFemaleFullWeight(){
		return averageFullWeight;
	}
	
	/**
	 * Get the approximate full grown weight for this breed for a Male.
	 * @return Approximate full grown weight for this breed
	 */
	public double getAverageMaleFullWeight(){
		return averageMaleFullWeight;
	}
	
	/**
	 * Get the average weight gain between birth and pubity
	 * @return Average weight gain between birth and pubity
	 */
	public double getAverageFemaleDailyGain(){
		return averageDailyGain;
	}
	
	/**
	 * Get the average weight gain between birth and pubity for a Male.
	 * @return Average weight gain between birth and pubity
	 */
	public double getAverageMaleDailyGain(){
		return averageMaleDailyGain;
	}
	
	/**
	 * Get the average time for gestation length in days
	 * @return Average time for gestation length in days
	 */
	public int getGestationPeriodInDays(){
		return gestationPeriodInDays;
	}
	
	/**
	 * Get the average number of days until pubity begins
	 * @return Average number of days until pubity begins
	 */
	public int getAverageFemalePubityAgeInDays(){
		return averagePubityAgeInDays;
	}
	
	/**
	 * Get the average number of days until pubity begins for a Male.
	 * @return Average number of days until pubity begins
	 */
	public int getAverageMalePubityAgeInDays(){
		return averageMalePubityAgeInDays;
	}
	
	/**
	 * Set the name of this breed
	 * @param name The name of this breed
	 */
	public void setBreedName(String name){
		if(!this.name.equals(name)){
			this.name=name;
			update.put("NAME", new UpdateHistory(this.name));
		}
	}
	
	/**
	 * Set the approximate birth weight of this breed
	 * @param averageCalfBirthWeight Approximate birth weight of this breed
	 */
	public void setAverageCalfBirthWeight(double averageCalfBirthWeight){
		if(this.averageCalfBirthWeight!=averageCalfBirthWeight){
			this.averageCalfBirthWeight=averageCalfBirthWeight;
			update.put("BIRTHWEIGHT", new UpdateHistory(String.valueOf(this.averageCalfBirthWeight)));
		}
	}
	
	/**
	 * Set the approximate full grown weight for this breed
	 * @param averageFullWeight Approximate full grown weight for this breed
	 */
	public void setAverageFemaleFullWeight(double averageFullWeight){
		if(this.averageFullWeight!=averageFullWeight){
			this.averageFullWeight=averageFullWeight;
			update.put("ADULTMALEWEIGHT", new UpdateHistory(String.valueOf(this.averageFullWeight)));
		}
	}
	
	/**
	 * Set the approximate male full grown weight for this breed
	 * @param averageMaleFullWeight Approximate full grown weight for this breed
	 */
	public void setAverageMaleFullWeight(double averageMaleFullWeight){
		if(this.averageMaleFullWeight!=averageMaleFullWeight){
			this.averageMaleFullWeight=averageMaleFullWeight;
			update.put("MALEFULLWEIGHT", new UpdateHistory(String.valueOf(this.averageMaleFullWeight)));
		}
	}
	
	/**
	 * Set the average weight gain between birth and pubity
	 * @param averageDailyGain Average weight gain between birth and pubity
	 */
	public void setAverageFemaleDailyGain(double averageDailyGain){
		if(this.averageDailyGain!=averageDailyGain){
			this.averageDailyGain=averageDailyGain;
			update.put("DAILYGAIN", new UpdateHistory(String.valueOf(this.averageDailyGain)));
		}
	}
	
	/**
	 * Set the average weight gain between birth and pubity of a Male
	 * @param averageMaleDailyGain Average weight gain between birth and pubity
	 */
	public void setAverageMaleDailyGain(double averageMaleDailyGain){
		if(this.averageMaleDailyGain!=averageMaleDailyGain){
			this.averageMaleDailyGain=averageMaleDailyGain;
			update.put("DAILYMALEGAIN", new UpdateHistory(String.valueOf(this.averageMaleDailyGain)));
		}
	}
	
	/**
	 * Set the average time for gestation length in days
	 * @param days Average time for gestation length in days
	 */
	public void setGestationPeriodInDays(int days){
		if(gestationPeriodInDays!=days){
			gestationPeriodInDays=days;
			update.put("GESTATIONLENGTH", new UpdateHistory(String.valueOf(this.gestationPeriodInDays)));
		}
	}
	
	/**
	 * Set the average number of days until pubity begins
	 * @param days Average number of days until pubity begins
	 */
	public void setAverageFemalePubityAgeInDays(int days){
		if(averagePubityAgeInDays!=days){
			averagePubityAgeInDays=days;
			update.put("PUBITY", new UpdateHistory(String.valueOf(this.averagePubityAgeInDays)));
		}
	}
	
	/**
	 * Set the average number of days until pubity begins in Males
	 * @param days Average number of days until pubity begins
	 */
	public void setAverageMalePubityAgeInDays(int days){
		if(averageMalePubityAgeInDays!=days){
			averageMalePubityAgeInDays=days;
			update.put("MALEPUBITY", new UpdateHistory(String.valueOf(this.averageMalePubityAgeInDays)));
		}
	}
	
	/**
	 * Get the unique id of this CattleBreed
	 * @return the id of this CattleBreed
	 */
	public long getID(){
		return id;
	}
	
	/**
	 * Updates this Object with the updated values from the different version.
	 * @param changes CattleBreed that represents this Object in a different version.
	 * @return <tt>true</tt> if changes is not null and changes does not equal this object, <tt>false</tt> otherwise.
	 */
	public boolean update(CattleBreed changes){
		if(changes!=null && this!=changes){
			if(updateBefore("NAME",changes)){
				name=changes.update.get("NAME").getValue();
				update.put("NAME", new UpdateHistory(name));
			}
			
			if(updateBefore("BIRTHWEIGHT",changes)){
				averageCalfBirthWeight=Double.parseDouble(changes.update.get("BIRTHWEIGHT").getValue());
				update.put("BIRTHWEIGHT", new UpdateHistory(String.valueOf(averageCalfBirthWeight)));
			}
			
			if(updateBefore("ADULTWEIGHT",changes)){
				averageFullWeight=Double.parseDouble(changes.update.get("ADULTWEIGHT").getValue());
				update.put("ADULTWEIGHT", new UpdateHistory(String.valueOf(averageFullWeight)));
			}
			
			if(updateBefore("DAILYGAIN",changes)){
				averageDailyGain=Double.parseDouble(changes.update.get("DAILYGAIN").getValue());
				update.put("DAILYGAIN", new UpdateHistory(String.valueOf(averageDailyGain)));
			}
			
			if(updateBefore("GESTATIONLENGTH",changes)){
				gestationPeriodInDays=Integer.parseInt(changes.update.get("GESTATIONLENGTH").getValue());
				update.put("GESTATIONLENGTH", new UpdateHistory(String.valueOf(gestationPeriodInDays)));
			}
			
			if(updateBefore("PUBITY",changes)){
				averagePubityAgeInDays=Integer.parseInt(changes.update.get("PUBITY").getValue());
				update.put("PUBITY", new UpdateHistory(String.valueOf(averagePubityAgeInDays)));
			}
			
			if(updateBefore("DAILYMALEGAIN",changes)){
				averageMaleDailyGain=Double.parseDouble(changes.update.get("DAILYMALEGAIN").getValue());
				update.put("DAILYMALEGAIN", new UpdateHistory(String.valueOf(averageMaleDailyGain)));
			}
			
			if(updateBefore("MALEFULLWEIGHT",changes)){
				averageMaleFullWeight=Double.parseDouble(changes.update.get("MALEFULLWEIGHT").getValue());
				update.put("MALEFULLWEIGHT", new UpdateHistory(String.valueOf(averageMaleFullWeight)));
			}
			
			if(updateBefore("MALEPUBITY",changes)){
				averageMalePubityAgeInDays=Integer.parseInt(changes.update.get("MALEPUBITY").getValue());
				update.put("MALEPUBITY", new UpdateHistory(String.valueOf(averageMalePubityAgeInDays)));
			}
			
			// Incase these values are not set, use default values.
			if(averageMaleDailyGain==0){setAverageMaleDailyGain(averageDailyGain);}
			if(averageMaleFullWeight==0){setAverageMaleFullWeight(averageFullWeight);}
			if(averageMalePubityAgeInDays==0){setAverageMalePubityAgeInDays(averagePubityAgeInDays);}
			
			return true;
		}
		return false;
	}
	
	/**
	 * Checks to see if the modification date of the item at this position is before the changed value
	 * @param key The String key at which to compare the modification dates of the modified objects.
	 * @param changes The CattleBreed that represents this CattleBreed on another system.
	 * @return <tt>true</tt> if the old value was modified before the changed value. <tt>false</tt> otherwise.
	 */
	protected boolean updateBefore(String key,CattleBreed changes){
		if(this.update.get(key)==null && changes.update.get(key)!=null){
			return true;
		}else if(changes.update.get(key)==null){
			return false;
		}
		return !this.update.get(key).getDate().after(changes.update.get(key).getDate());
	}
}
